### Eclipse Workspace Patch 1.0 #P moodle20 Index: mod/quiz/db/access.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/db/access.php,v retrieving revision 1.15 diff -u -r1.15 access.php --- mod/quiz/db/access.php 19 Nov 2009 17:31:41 -0000 1.15 +++ mod/quiz/db/access.php 5 Mar 2010 05:06:51 -0000 @@ -52,6 +52,16 @@ ) ), + // Edit the quiz overrides + 'mod/quiz:manageoverrides' => array( + 'captype' => 'write', + 'contextlevel' => CONTEXT_MODULE, + 'legacy' => array( + 'editingteacher' => CAP_ALLOW, + 'admin' => CAP_ALLOW + ) + ), + // Preview the quiz. 'mod/quiz:preview' => array( 'captype' => 'write', // Only just a write. Index: mod/quiz/db/install.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/db/install.php,v retrieving revision 1.3 diff -u -r1.3 install.php --- mod/quiz/db/install.php 4 Nov 2009 11:58:33 -0000 1.3 +++ mod/quiz/db/install.php 5 Mar 2010 05:06:51 -0000 @@ -21,6 +21,8 @@ update_log_display_entry('quiz', 'start attempt', 'quiz', 'name'); update_log_display_entry('quiz', 'close attempt', 'quiz', 'name'); update_log_display_entry('quiz', 'continue attempt', 'quiz', 'name'); + update_log_display_entry('quiz', 'edit override', 'quiz', 'name'); + update_log_display_entry('quiz', 'delete override', 'quiz', 'name'); $record = new object(); $record->name = 'overview'; Index: mod/quiz/db/upgrade.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/db/upgrade.php,v retrieving revision 1.33 diff -u -r1.33 upgrade.php --- mod/quiz/db/upgrade.php 4 Nov 2009 11:58:33 -0000 1.33 +++ mod/quiz/db/upgrade.php 5 Mar 2010 05:06:51 -0000 @@ -292,6 +292,40 @@ upgrade_mod_savepoint($result, 2009042000, 'quiz'); } + if ($result && $oldversion < 2010030501) { + /// fix log actions + update_log_display_entry('quiz', 'edit override', 'quiz', 'name'); + update_log_display_entry('quiz', 'delete override', 'quiz', 'name'); + + /// Define table quiz_overrides to be created + $table = new xmldb_table('quiz_overrides'); + + /// Adding fields to table quiz_overrides + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('quiz', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0'); + $table->add_field('groupid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null); + $table->add_field('timeopen', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null); + $table->add_field('timeclose', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null); + $table->add_field('timelimit', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, null, null, null); + $table->add_field('attempts', XMLDB_TYPE_INTEGER, '6', XMLDB_UNSIGNED, null, null, null); + $table->add_field('password', XMLDB_TYPE_CHAR, '255', null, null, null, null); + + /// Adding keys to table quiz_overrides + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('quiz', XMLDB_KEY_FOREIGN, array('quiz'), 'quiz', array('id')); + $table->add_key('groupid', XMLDB_KEY_FOREIGN, array('groupid'), 'groups', array('id')); + $table->add_key('userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id')); + + /// Conditionally launch create table for quiz_overrides + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + /// quiz savepoint reached + upgrade_mod_savepoint($result, 2010030501, 'quiz'); + } + return $result; } Index: mod/quiz/db/install.xml =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/db/install.xml,v retrieving revision 1.23 diff -u -r1.23 install.xml --- mod/quiz/db/install.xml 1 May 2009 14:07:48 -0000 1.23 +++ mod/quiz/db/install.xml 5 Mar 2010 05:06:51 -0000 @@ -1,5 +1,5 @@ - @@ -109,7 +109,7 @@ - +
@@ -122,5 +122,24 @@
+ + + + + + + + + + + + + + + + + + +
\ No newline at end of file Index: mod/quiz/backuplib.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/backuplib.php,v retrieving revision 1.75 diff -u -r1.75 backuplib.php --- mod/quiz/backuplib.php 4 Nov 2009 11:58:32 -0000 1.75 +++ mod/quiz/backuplib.php 5 Mar 2010 05:06:49 -0000 @@ -279,6 +279,8 @@ $status = backup_quiz_question_instances($bf,$preferences,$quiz->id); //Now we print to xml quiz_feedback (Course Level) $status = backup_quiz_feedback($bf,$preferences,$quiz->id); + //Now we print to xml quiz_overrides (Course Level) + $status = backup_quiz_overrides($bf,$preferences,$quiz->id); //if we've selected to backup users info, then execute: // - backup_quiz_grades // - backup_quiz_attempts @@ -341,6 +343,42 @@ return $status; } + //Backup quiz_overrides contents (executed from quiz_backup_mods) + function backup_quiz_overrides ($bf,$preferences,$quiz) { + global $DB; + $status = true; + $douserdata = backup_userdata_selected($preferences,'quiz',$quiz); + + $quiz_overrides = $DB->get_records('quiz_overrides',array('quiz' =>$quiz),'id'); + //If there are quiz_overrides + if ($quiz_overrides) { + //Write start tag + $status = fwrite ($bf,start_tag("OVERRIDES",4,true)); + //Iterate over each quiz_override + foreach ($quiz_overrides as $quiz_override) { + // Only backup user overrides if we are backing up user data + if ($douserdata || $quiz_override->userid == 0) { + //Start quiz override + $status = fwrite ($bf,start_tag("OVERRIDE",5,true)); + //Print quiz_override contents + fwrite ($bf,full_tag("ID",6,false,$quiz_override->id)); + fwrite ($bf,full_tag("USERID",6,false,$quiz_override->userid)); + fwrite ($bf,full_tag("GROUPID",6,false,$quiz_override->groupid)); + fwrite ($bf,full_tag("TIMEOPEN",6,false,$quiz_override->timeopen)); + fwrite ($bf,full_tag("TIMECLOSE",6,false,$quiz_override->timeclose)); + fwrite ($bf,full_tag("TIMELIMIT",6,false,$quiz_override->timelimit)); + fwrite ($bf,full_tag("ATTEMPTS",6,false,$quiz_override->attempts)); + fwrite ($bf,full_tag("PASSWORD",6,false,$quiz_override->password)); + //End quiz override + $status = fwrite ($bf,end_tag("OVERRIDE",5,true)); + } + } + //Write end tag + $status = fwrite ($bf,end_tag("OVERRIDES",4,true)); + } + return $status; + } + //Backup quiz_question_instances contents (executed from quiz_backup_mods) function backup_quiz_feedback ($bf,$preferences,$quiz) { global $DB; Index: mod/quiz/version.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/version.php,v retrieving revision 1.144 diff -u -r1.144 version.php --- mod/quiz/version.php 19 Nov 2009 17:31:41 -0000 1.144 +++ mod/quiz/version.php 5 Mar 2010 05:06:51 -0000 @@ -5,7 +5,7 @@ // This fragment is called by moodle_needs_upgrading() and /admin/index.php //////////////////////////////////////////////////////////////////////////////// -$module->version = 2009111900; // The (date) version of this module +$module->version = 2010030501; // The (date) version of this module $module->requires = 2009041700; // Requires this Moodle version $module->cron = 0; // How often should cron check this module (seconds)? Index: mod/quiz/summary.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/summary.php,v retrieving revision 1.26 diff -u -r1.26 summary.php --- mod/quiz/summary.php 18 Jan 2010 20:57:35 -0000 1.26 +++ mod/quiz/summary.php 5 Mar 2010 05:06:51 -0000 @@ -14,7 +14,7 @@ $PAGE->set_url('/mod/quiz/summary.php', array('attempt'=>$attemptid)); -$attemptobj = new quiz_attempt($attemptid); +$attemptobj = quiz_attempt::create($attemptid); /// Check login. require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); Index: mod/quiz/startattempt.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/startattempt.php,v retrieving revision 1.7 diff -u -r1.7 startattempt.php --- mod/quiz/startattempt.php 4 Nov 2009 11:58:31 -0000 1.7 +++ mod/quiz/startattempt.php 5 Mar 2010 05:06:51 -0000 @@ -28,7 +28,7 @@ print_error('invalidcoursemodule'); } -$quizobj = new quiz($quiz, $cm, $course); +$quizobj = quiz::create($quiz->id, $USER->id); /// Check login and sesskey. require_login($quizobj->get_courseid(), false, $quizobj->get_cm()); Index: mod/quiz/attemptlib.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/attemptlib.php,v retrieving revision 1.56 diff -u -r1.56 attemptlib.php --- mod/quiz/attemptlib.php 6 Feb 2010 14:09:33 -0000 1.56 +++ mod/quiz/attemptlib.php 5 Mar 2010 05:06:49 -0000 @@ -90,6 +90,32 @@ $this->determine_layout(); } + /** + * Static function to create a new quiz object for a specific user. + * + * @param integer $quizid the the quiz id. + * @param integer $userid the the userid. + * @return object the new quiz object + */ + static public function create($quizid, $userid) { + global $DB; + + if (!$quiz = $DB->get_record('quiz', array('id' => $quizid))) { + throw new moodle_exception('invalidquizid', 'quiz'); + } + if (!$course = $DB->get_record('course', array('id' => $quiz->course))) { + throw new moodle_exception('invalidcoursemodule'); + } + if (!$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id)) { + throw new moodle_exception('invalidcoursemodule'); + } + + // Update quiz with override information + $quiz = quiz_update_effective_access($quiz, $userid); + + return new quiz($quiz, $cm, $course); + } + // Functions for loading more data ===================================================== /** * Convenience method. Calls {@link load_questions()} with the list of @@ -418,17 +444,33 @@ // Constructor ========================================================================= /** - * Constructor from just an attemptid. + * Constructor assuming we already have the necessary data loaded. * - * @param integer $attemptid the id of the attempt to load. We automatically load the - * associated quiz, course, etc. + * @param object $attempt the row of the quiz_attempts table. + * @param object $quiz the quiz object for this attempt and user. + * @param object $cm the course_module object for this quiz. + * @param object $course the row from the course table for the course we belong to. */ - function __construct($attemptid) { + function __construct($attempt, $quiz, $cm, $course) { + $this->attempt = $attempt; + parent::__construct($quiz, $cm, $course); + $this->preload_questions(); + $this->preload_question_states(); + } + + /** + * Static function to create a new quiz_attempt object given an attemptid. + * + * @param integer $attemptid the attempt id. + * @return object the new quiz_attempt object + */ + static public function create($attemptid) { global $DB; - if (!$this->attempt = quiz_load_attempt($attemptid)) { + + if (!$attempt = quiz_load_attempt($attemptid)) { throw new moodle_exception('invalidattemptid', 'quiz'); } - if (!$quiz = $DB->get_record('quiz', array('id' => $this->attempt->quiz))) { + if (!$quiz = $DB->get_record('quiz', array('id' => $attempt->quiz))) { throw new moodle_exception('invalidquizid', 'quiz'); } if (!$course = $DB->get_record('course', array('id' => $quiz->course))) { @@ -437,9 +479,10 @@ if (!$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id)) { throw new moodle_exception('invalidcoursemodule'); } - parent::__construct($quiz, $cm, $course); - $this->preload_questions(); - $this->preload_question_states(); + // Update quiz with override information + $quiz = quiz_update_effective_access($quiz, $attempt->userid); + + return new quiz_attempt($attempt, $quiz, $cm, $course); } // Functions for loading more data ===================================================== Index: mod/quiz/review.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/review.php,v retrieving revision 1.110 diff -u -r1.110 review.php --- mod/quiz/review.php 6 Feb 2010 14:09:33 -0000 1.110 +++ mod/quiz/review.php 5 Mar 2010 05:06:51 -0000 @@ -24,7 +24,7 @@ } $PAGE->set_url($url); - $attemptobj = new quiz_attempt($attemptid); + $attemptobj = quiz_attempt::create($attemptid); /// Check login. require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); Index: mod/quiz/accessrules.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/accessrules.php,v retrieving revision 1.38 diff -u -r1.38 accessrules.php --- mod/quiz/accessrules.php 26 Jan 2010 09:42:19 -0000 1.38 +++ mod/quiz/accessrules.php 5 Mar 2010 05:06:48 -0000 @@ -626,7 +626,19 @@ /// If they entered the right password, let them in. $enteredpassword = optional_param('quizpassword', '', PARAM_RAW); + $validpassword = false; if (strcmp($this->_quiz->password, $enteredpassword) === 0) { + $validpassword = true; + } else if (isset($this->_quiz->extrapasswords)) { + // group overrides may have additional passwords + foreach ($this->_quiz->extrapasswords as $password) { + if (strcmp($password, $enteredpassword) === 0) { + $validpassword = true; + break; + } + } + } + if ($validpassword) { $SESSION->passwordcheckedquizzes[$this->_quiz->id] = true; return; } Index: mod/quiz/attempt.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/attempt.php,v retrieving revision 1.179 diff -u -r1.179 attempt.php --- mod/quiz/attempt.php 6 Feb 2010 14:09:33 -0000 1.179 +++ mod/quiz/attempt.php 5 Mar 2010 05:06:48 -0000 @@ -33,7 +33,7 @@ } $PAGE->set_url($url); - $attemptobj = new quiz_attempt($attemptid); + $attemptobj = quiz_attempt::create($attemptid); /// Check login. require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); Index: mod/quiz/comment.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/comment.php,v retrieving revision 1.21 diff -u -r1.21 comment.php --- mod/quiz/comment.php 16 Jan 2010 15:40:05 -0000 1.21 +++ mod/quiz/comment.php 5 Mar 2010 05:06:49 -0000 @@ -15,7 +15,7 @@ $PAGE->set_url('/mod/quiz/comment.php', array('attempt'=>$attemptid, 'question'=>$questionid)); - $attemptobj = new quiz_attempt($attemptid); + $attemptobj = quiz_attempt::create($attemptid); /// Can only grade finished attempts. if (!$attemptobj->is_finished()) { Index: mod/quiz/processattempt.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/processattempt.php,v retrieving revision 1.10 diff -u -r1.10 processattempt.php --- mod/quiz/processattempt.php 4 Nov 2009 11:58:31 -0000 1.10 +++ mod/quiz/processattempt.php 5 Mar 2010 05:06:50 -0000 @@ -27,7 +27,7 @@ $finishattempt = optional_param('finishattempt', 0, PARAM_BOOL); $timeup = optional_param('timeup', 0, PARAM_BOOL); // True if form was submitted by timer. -$attemptobj = new quiz_attempt($attemptid); +$attemptobj = quiz_attempt::create($attemptid); /// Set $nexturl now. It will be updated if a particular question was sumbitted in /// adaptive mode. Index: mod/quiz/view.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/view.php,v retrieving revision 1.183 diff -u -r1.183 view.php --- mod/quiz/view.php 11 Feb 2010 13:27:03 -0000 1.183 +++ mod/quiz/view.php 5 Mar 2010 05:06:51 -0000 @@ -44,7 +44,7 @@ /// Create an object to manage all the other (non-roles) access rules. $timenow = time(); - $accessmanager = new quiz_access_manager(new quiz($quiz, $cm, $course), $timenow, + $accessmanager = new quiz_access_manager(quiz::create($quiz->id, $USER->id), $timenow, has_capability('mod/quiz:ignoretimelimits', $context, NULL, false)); /// If no questions have been set up yet redirect to edit.php @@ -82,7 +82,7 @@ $PAGE->set_title($title); $PAGE->set_heading($course->fullname); - + echo $OUTPUT->header(); /// Print heading and tabs (if there is more than one). @@ -129,6 +129,9 @@ exit; } +/// Update the quiz with overrides for the current user + $quiz = quiz_update_effective_access($quiz, $USER->id); + /// Get this user's attempts. $attempts = quiz_get_user_attempts($quiz->id, $USER->id); $lastfinishedattempt = end($attempts); Index: mod/quiz/tabs.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/tabs.php,v retrieving revision 1.40 diff -u -r1.40 tabs.php --- mod/quiz/tabs.php 17 Jan 2010 10:54:13 -0000 1.40 +++ mod/quiz/tabs.php 5 Mar 2010 05:06:51 -0000 @@ -45,6 +45,9 @@ if (has_capability('mod/quiz:manage', $context)) { $row[] = new tabobject('edit', "$CFG->wwwroot/mod/quiz/edit.php?cmid=$cm->id", "pix_url('t/edit') . "\" class=\"iconsmall\" alt=\"$stredit\" /> $stredit",$stredit); } +if (has_capability('mod/quiz:manageoverrides', $context)) { + $row[] = new tabobject('overrides', "$CFG->wwwroot/mod/quiz/overrides.php?cmid=$cm->id", get_string('overrides', 'quiz')); +} if ($currenttab == 'info' && count($row) == 1) { // Don't show only an info tab (e.g. to students). @@ -91,6 +94,20 @@ } +if ($currenttab == 'overrides' and isset($mode)) { + $activated[] = 'overrides'; + + $row = array(); + $currenttab = $mode; + + $strgroup = get_string('groupoverrides', 'quiz'); + $struser = get_string('useroverrides', 'quiz'); + + $row[] = new tabobject('group', "$CFG->wwwroot/mod/quiz/overrides.php?cmid=$cm->id", $strgroup); + $row[] = new tabobject('user', "$CFG->wwwroot/mod/quiz/overrides.php?cmid=$cm->id&mode=user", $struser); + $tabs[] = $row; +} + if (!$quiz->questions) { $inactive += array('info', 'reports', 'preview'); } Index: mod/quiz/lib.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/lib.php,v retrieving revision 1.355 diff -u -r1.355 lib.php --- mod/quiz/lib.php 14 Feb 2010 09:29:44 -0000 1.355 +++ mod/quiz/lib.php 5 Mar 2010 05:06:50 -0000 @@ -179,6 +179,7 @@ } quiz_delete_all_attempts($quiz); + quiz_delete_all_overrides($quiz); $DB->delete_records('quiz_question_instances', array('quiz' => $quiz->id)); $DB->delete_records('quiz_feedback', array('quizid' => $quiz->id)); @@ -196,6 +197,148 @@ } /** + * Deletes a quiz override from the database and clears any corresponding calendar events + * + * @param object $quiz The quiz object. + * @param integer $overrideid The id of the override being deleted + * @return bool true on success + */ +function quiz_delete_override($quiz, $overrideid) { + global $DB; + + if (!$override = $DB->get_record('quiz_overrides', array('id' => $overrideid))) { + return false; + } + $groupid = empty($override->groupid)? 0 : $override->groupid; + $userid = empty($override->userid)? 0 : $override->userid; + + // Delete the events + $events = $DB->get_records('event', array('modulename'=>'quiz', 'instance'=>$quiz->id, 'groupid'=>$groupid, 'userid'=>$userid)); + foreach($events as $event) { + $eventold = calendar_event::load($event); + $eventold->delete(); + } + + $DB->delete_records('quiz_overrides', array('id' => $overrideid)); + return true; +} + +/** + * Deletes all quiz overrides from the database and clears any corresponding calendar events + * + * @param object $quiz The quiz object. + */ +function quiz_delete_all_overrides($quiz) { + global $DB; + + $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id), 'id'); + foreach ($overrides as $override) { + quiz_delete_override($quiz, $override->id); + } +} + +/** + * Updates a quiz object with override information for a user. + * + * Algorithm: For each quiz setting, if there is a matching user-specific override, + * then use that otherwise, if there are group-specific overrides, return the most + * lenient combination of them. If neither applies, leave the quiz setting unchanged. + * + * Special case: if there is more than one password that applies to the user, then + * quiz->extrapasswords will contain an array of strings giving the remaining + * passwords. + * + * @param object $quiz The quiz object. + * @param integer $userid The userid. + * @return object $quiz The updated quiz object. + */ +function quiz_update_effective_access($quiz, $userid) { + global $DB; + + // check for user override + $override = $DB->get_record('quiz_overrides', array('quiz' => $quiz->id, 'userid' => $userid)); + + if (!$override) { + $override = new stdclass; + $override->timeopen = null; + $override->timeclose = null; + $override->timelimit = null; + $override->attempts = null; + $override->password = null; + } + + // check for group overrides + $groupings = groups_get_user_groups($quiz->course, $userid); + $groupingid = empty($cm->groupingid)? 0 : $cm->groupingid; + + if (!empty($groupings[$groupingid])) { + + // Select all overrides that apply to the User's groups + list($extra, $params) = $DB->get_in_or_equal(array_values($groupings[$groupingid])); + $SQL = "SELECT * + FROM {quiz_overrides} + WHERE quiz = ".$quiz->id." + AND groupid $extra"; + $records = $DB->get_records_sql($SQL, $params); + + // Combine the overrides + $opens = array(); + $closes = array(); + $limits = array(); + $attempts = array(); + $passwords = array(); + + foreach ($records as $gpoverride) { + if (isset($gpoverride->timeopen)) { + $opens[] = $gpoverride->timeopen; + } + if (isset($gpoverride->timeclose)) { + $closes[] = $gpoverride->timeclose; + } + if (isset($gpoverride->timelimit)) { + $limits[] = $gpoverride->timelimit; + } + if (isset($gpoverride->attempts)) { + $attempts[] = $gpoverride->attempts; + } + if (isset($gpoverride->password)) { + $passwords[] = $gpoverride->password; + } + } + // If there is a user override for a setting, ignore the group override + if (is_null($override->timeopen) && count($opens)) { + $override->timeopen = min($opens); + } + if (is_null($override->timeclose) && count($closes)) { + $override->timeclose = max($closes); + } + if (is_null($override->timelimit) && count($limits)) { + $override->timelimit = max($limits); + } + if (is_null($override->attempts) && count($attempts)) { + $override->attempts = max($attempts); + } + if (is_null($override->password) && count($passwords)) { + $override->password = array_shift($passwords); + if (count($passwords)) { + $override->extrapasswords = $passwords; + } + } + + } + + // merge with quiz defaults + $keys = array('timeopen','timeclose', 'timelimit', 'attempts', 'password', 'extrapasswords'); + foreach ($keys as $key) { + if (isset($override->{$key})) { + $quiz->{$key} = $override->{$key}; + } + } + + return $quiz; +} + +/** * Delete all the attempts belonging to a quiz. * * @global stdClass @@ -639,74 +782,11 @@ return true; } } - $moduleid = $DB->get_field('modules', 'id', array('name' => 'quiz')); foreach ($quizzes as $quiz) { - $cm = get_coursemodule_from_id('quiz', $quiz->id); - $event = NULL; - $event2 = NULL; - $event2old = NULL; - - if ($events = $DB->get_records('event', array('modulename' => 'quiz', 'instance' => $quiz->id), 'timestart')) { - $event = array_shift($events); - if (!empty($events)) { - $event2old = array_shift($events); - if (!empty($events)) { - foreach ($events as $badevent) { - $badevent = calendar_event::load($badevent); - $badevent->delete(); - } - } - } - } - - $event->name = $quiz->name; - $event->description = format_module_intro('quiz', $quiz, $cm->id); - $event->courseid = $quiz->course; - $event->groupid = 0; - $event->userid = 0; - $event->modulename = 'quiz'; - $event->instance = $quiz->id; - $event->visible = instance_is_visible('quiz', $quiz); - $event->timestart = $quiz->timeopen; - $event->eventtype = 'open'; - $event->timeduration = ($quiz->timeclose - $quiz->timeopen); - - if ($event->timeduration > QUIZ_MAX_EVENT_LENGTH) { /// Set up two events - - $event2 = $event; - - $event->name = $quiz->name.' ('.get_string('quizopens', 'quiz').')'; - $event->timeduration = 0; - - $event2->name = $quiz->name.' ('.get_string('quizcloses', 'quiz').')'; - $event2->timestart = $quiz->timeclose; - $event2->eventtype = 'close'; - $event2->timeduration = 0; - - if (empty($event2old->id)) { - unset($event2->id); - calendar_event::create($event2); - } else { - $event2->id = $event2old->id; - $event2 = calendar_event::load($event2); - $event2->update($event2); - } - } else if (!empty($event2old->id)) { - $event2old = calendar_event::load($event2old); - $event2old->delete(); - } - - if (empty($event->id)) { - if (!empty($event->timestart)) { - calendar_event::create($event); - } - } else { - $event = calendar_event::load($event); - $event->update($event); - } - + quiz_update_events($quiz); } + return true; } @@ -1079,49 +1159,135 @@ } // Update the events relating to this quiz. - // This is slightly inefficient, deleting the old events and creating new ones. However, - // there are at most two events, and this keeps the code simpler. - if ($events = $DB->get_records('event', array('modulename'=>'quiz', 'instance'=>$quiz->id))) { - foreach($events as $event) { - $event2old = calendar_event::load($event); - $event2old->delete(); - } - } + quiz_update_events($quiz); - $event = new stdClass; - $event->description = $quiz->intro; - $event->courseid = $quiz->course; - $event->groupid = 0; - $event->userid = 0; - $event->modulename = 'quiz'; - $event->instance = $quiz->id; - $event->timestart = $quiz->timeopen; - $event->timeduration = $quiz->timeclose - $quiz->timeopen; - $event->visible = instance_is_visible('quiz', $quiz); - $event->eventtype = 'open'; - - if ($quiz->timeclose and $quiz->timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) { - // Single event for the whole quiz. - $event->name = $quiz->name; - calendar_event::create($event); - } else { - // Separate start and end events. - $event->timeduration = 0; - if ($quiz->timeopen) { - $event->name = $quiz->name.' ('.get_string('quizopens', 'quiz').')'; - calendar_event::create($event); - unset($event->id); // So we can use the same object for the close event. + //update related grade item + quiz_grade_item_update($quiz); + +} + +/** + * This function updates the events associated to the quiz. + * If $override is non-zero, then it updates only the events + * associated with the specified override. + * + * @uses QUIZ_MAX_EVENT_LENGTH + * @param object $quiz the quiz object. + * @param object optional $override limit to a specific override + */ +function quiz_update_events($quiz, $override=null) { + global $DB; + + // Load the old events relating to this quiz. + $conds = array('modulename'=>'quiz', + 'instance'=>$quiz->id); + if (!empty($override)) { + // only load events for this override + $conds['groupid'] = isset($override->groupid)? $override->groupid : 0; + $conds['userid'] = isset($override->userid)? $override->userid : 0; + } + $oldevents = $DB->get_records('event', $conds); + + // Now make a todo list of all that needs to be updated + if (empty($override)) { + // We are updating the primary settings for the quiz, so we + // need to add all the overrides + $overrides = $DB->get_records('quiz_overrides', array('quiz' => $quiz->id)); + // as well as the original quiz (empty override) + $overrides[] = new stdClass; + } + else { + // Just do the one override + $overrides = array($override); + } + + foreach ($overrides as $current) { + $groupid = isset($current->groupid)? $current->groupid : 0; + $userid = isset($current->userid)? $current->userid : 0; + $timeopen = isset($current->timeopen)? $current->timeopen : $quiz->timeopen; + $timeclose = isset($current->timeclose)? $current->timeclose : $quiz->timeclose; + + // only add open/close events for an override if they differ from the quiz default + $addopen = empty($current->id) || !empty($current->timeopen); + $addclose = empty($current->id) || !empty($current->timeclose); + + $event = new stdClass; + $event->description = $quiz->intro; + $event->courseid = ($userid)? 0 : $quiz->course; // Events module won't show user events when the courseid is nonzero + $event->groupid = $groupid; + $event->userid = $userid; + $event->modulename = 'quiz'; + $event->instance = $quiz->id; + $event->timestart = $timeopen; + $event->timeduration = max($timeclose - $timeopen, 0); + $event->visible = instance_is_visible('quiz', $quiz); + $event->eventtype = 'open'; + + // Determine the event name + if ($groupid) { + $params = new stdClass; + $params->quiz = $quiz->name; + $params->group = groups_get_group_name($groupid); + if ($params->group === false) { + // group doesn't exist, just skip it + continue; + } + $eventname = get_string('overridegroupeventname', 'quiz', $params); } - if ($quiz->timeclose) { - $event->name = $quiz->name.' ('.get_string('quizcloses', 'quiz').')'; - $event->timestart = $quiz->timeclose; - $event->eventtype = 'close'; - calendar_event::create($event); + else if ($userid) { + $params = new stdClass; + $params->quiz = $quiz->name; + $eventname = get_string('overrideusereventname', 'quiz', $params); + } else { + $eventname = $quiz->name; + } + if ($addopen or $addclose) { + if ($timeclose and $timeopen and $event->timeduration <= QUIZ_MAX_EVENT_LENGTH) { + // Single event for the whole quiz. + if ($oldevent = array_shift($oldevents)) { + $event->id = $oldevent->id; + } + else { + unset($event->id); + } + $event->name = $eventname; + // calendar_event::create will reuse a db record if the id field is set + calendar_event::create($event); + } else { + // Separate start and end events. + $event->timeduration = 0; + if ($timeopen && $addopen) { + if ($oldevent = array_shift($oldevents)) { + $event->id = $oldevent->id; + } + else { + unset($event->id); + } + $event->name = $eventname.' ('.get_string('quizopens', 'quiz').')'; + // calendar_event::create will reuse a db record if the id field is set + calendar_event::create($event); + } + if ($timeclose && $addclose) { + if ($oldevent = array_shift($oldevents)) { + $event->id = $oldevent->id; + } + else { + unset($event->id); + } + $event->name = $eventname.' ('.get_string('quizcloses', 'quiz').')'; + $event->timestart = $timeclose; + $event->eventtype = 'close'; + calendar_event::create($event); + } + } } } - //update related grade item - quiz_grade_item_update($quiz); + // Delete any leftover events + foreach ($oldevents as $badevent) { + $badevent = calendar_event::load($badevent); + $badevent->delete(); + } } /** Index: mod/quiz/restorelib.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/restorelib.php,v retrieving revision 1.97 diff -u -r1.97 restorelib.php --- mod/quiz/restorelib.php 4 Nov 2009 11:58:32 -0000 1.97 +++ mod/quiz/restorelib.php 5 Mar 2010 05:06:50 -0000 @@ -113,6 +113,8 @@ $status = quiz_question_instances_restore_mods($newid,$info,$restore); //We have to restore the feedback now (course level table) $status = quiz_feedback_restore_mods($newid, $info, $restore, $quiz); + //We have to restore the overrides now (course level table) + $status = quiz_overrides_restore_mods($newid, $info, $restore, $quiz); //Now check if want to restore user data and do it. if (restore_userdata_selected($restore,'quiz',$mod->id)) { //Restore quiz_attempts @@ -241,6 +243,78 @@ return $status; } + //This function restores the quiz_overrides + function quiz_overrides_restore_mods($quiz_id, $info, $restore, $quiz) { + global $DB; + + $douserdata = restore_userdata_selected($restore,'quiz',$quiz_id); + + $status = true; + + //Get the quiz_feedback array + if (array_key_exists('OVERRIDES', $info['MOD']['#'])) { + $overrides = $info['MOD']['#']['OVERRIDES']['0']['#']['OVERRIDE']; + + //Iterate over the feedbacks + foreach ($overrides as $override_info) { + //traverse_xmlize($override_info); //Debug + //print_object ($GLOBALS['traverse_array']); //Debug + //$GLOBALS['traverse_array']=""; //Debug + + //We'll need this later!! + $oldid = backup_todb($override_info['#']['ID']['0']['#']); + + //Now, build the quiz_overrides record structure + $override = new stdClass(); + $override->quiz = $quiz_id; + $override->groupid = backup_todb($override_info['#']['GROUPID']['0']['#']); + $override->userid = backup_todb($override_info['#']['USERID']['0']['#']); + $override->timeopen = backup_todb($override_info['#']['TIMEOPEN']['0']['#']); + $override->timeclose = backup_todb($override_info['#']['TIMECLOSE']['0']['#']); + $override->timelimit = backup_todb($override_info['#']['TIMELIMIT']['0']['#']); + $override->attempts = backup_todb($override_info['#']['ATTEMPTS']['0']['#']); + $override->password = backup_todb($override_info['#']['PASSWORD']['0']['#']); + + // Only restore user overrides if we are restoring user data + if ($douserdata || $override->userid == 0) { + + //We have to recode the userid field + if ($override->userid) { + if (!$user = backup_getid($restore->backup_unique_code,"user",$override->userid)) { + debugging("override can not be restored, user id $override->userid not present in backup"); + // do not not block the restore + continue; + } + $override->userid = $user->new_id; + + } + + //We have to recode the groupid field + if ($override->groupid) { + if (!$group = backup_getid($restore->backup_unique_code,"groups",$override->groupid)) { + debugging("override can not be restored, group id $override->groupid not present in backup"); + // do not not block the restore + continue; + } + $override->groupid = $group->new_id; + } + + //The structure is equal to the db, so insert the quiz_question_instances + $newid = $DB->insert_record('quiz_overrides', $override); + + if ($newid) { + //We have the newid, update backup_ids + backup_putid($restore->backup_unique_code, 'quiz_overrides', $oldid, $newid); + } else { + $status = false; + } + } + } + } + + return $status; + } + //This function restores the quiz_attempts function quiz_attempts_restore_mods($quiz_id,$info,$restore) { global $CFG, $DB; Index: mod/quiz/reviewquestion.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/reviewquestion.php,v retrieving revision 1.45 diff -u -r1.45 reviewquestion.php --- mod/quiz/reviewquestion.php 6 Feb 2010 14:09:33 -0000 1.45 +++ mod/quiz/reviewquestion.php 5 Mar 2010 05:06:51 -0000 @@ -21,7 +21,7 @@ } $PAGE->set_url($url); - $attemptobj = new quiz_attempt($attemptid); + $attemptobj = quiz_attempt::create($attemptid); /// Check login. require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm()); Index: lang/en_utf8/quiz.php =================================================================== RCS file: /cvsroot/moodle/moodle/lang/en_utf8/quiz.php,v retrieving revision 1.143 diff -u -r1.143 quiz.php --- lang/en_utf8/quiz.php 30 Sep 2009 10:57:57 -0000 1.143 +++ lang/en_utf8/quiz.php 5 Mar 2010 05:06:48 -0000 @@ -26,8 +26,10 @@ $string['addingshortanswer'] = 'Adding a Short-Answer question'; $string['addingtruefalse'] = 'Adding a True/False question'; $string['addmoreoverallfeedbacks'] = 'Add {no} more feedback fields'; +$string['addnewgroupoverride'] = 'Add group override'; $string['addnewpagesafterselected'] = 'Add new pages after selected questions'; $string['addnewquestionsqbank'] = 'Add questions to the category $a->catname: $a->link'; +$string['addnewuseroverride'] = 'Add user override'; $string['addpagehere'] = 'Add page here'; $string['addquestion'] = 'Add question'; $string['addquestions'] = 'Add questions'; @@ -220,6 +222,7 @@ $string['deleteselected'] = 'Delete selected'; $string['deletingquestionattempts'] = 'Deleting question attempts'; $string['description'] = 'Description'; +$string['disabled'] = 'Disabled'; $string['discrimination'] = 'Discrim. Index'; $string['displayoptions'] = 'Display options'; $string['download'] = 'Click to download the exported category file'; @@ -244,6 +247,7 @@ $string['editingrqp'] = '$a: editing a question'; $string['editingshortanswer'] = 'Editing a Short-Answer question'; $string['editingtruefalse'] = 'Editing a True/False question'; +$string['editoverride'] = 'Edit override'; $string['editqcats'] = 'Edit questions categories'; $string['editquestions'] = 'Edit questions'; $string['editquiz'] = 'Edit Quiz'; @@ -268,6 +272,7 @@ You can review this attempt at $a->quizreviewurl.'; $string['emailnotifysubject'] = '$a->studentname has completed quiz $a->quizname'; $string['empty'] = 'Empty'; +$string['enabled'] = 'Enabled'; $string['endtest'] = 'End test ...'; $string['erroraccessingreport'] = 'You cannot access this report'; $string['errorinquestion'] = 'Error in question'; @@ -350,6 +355,7 @@ $string['gradingdetailspenalty'] = 'This submission attracted a penalty of $a.'; $string['gradingdetailszeropenalty'] = 'You were not penalized for this submission.'; $string['gradingmethod'] = 'Grading method: $a'; +$string['groupoverrides'] = 'Group Overrides'; $string['guestsno'] = 'Sorry, guests cannot see or attempt quizzes'; $string['hidebreaks'] = 'Hide page breaks'; $string['hidereordertool'] = 'Hide the reordering tool'; @@ -380,6 +386,7 @@ $string['invalidcategory'] = 'Category ID is invalid'; $string['invalidnumericanswer'] = 'One of the answers you entered was not a valid number.'; $string['invalidnumerictolerance'] = 'One of the tolerances you entered was not a valid number.'; +$string['invalidoverrideid'] = 'Invalid override id'; $string['invalidquestionid'] = 'Invalid question id'; $string['invalidquizid'] = 'Invalid Quiz ID'; $string['invalidsource'] = 'The source is not accepted as valid.'; @@ -444,6 +451,7 @@ $string['noattempts'] = 'No attempts have been made on this quiz'; $string['noattemptstoshow'] = 'There are no attempts to show'; $string['nocategory'] = 'Incorrect or no category specified'; +$string['noclose'] = 'No close date'; $string['nocommentsyet'] = 'No comments yet.'; $string['noconnection'] = 'There is currently no connection to a web service that can process this question. Please contact your administrator'; $string['nodataset'] = 'nothing - it is not a wild card'; @@ -454,6 +462,8 @@ $string['nominal'] = 'Nominal'; $string['nomoreattempts'] = 'No more attempts are allowed'; $string['none'] = 'None'; +$string['noopen'] = 'No open date'; +$string['nooverridedata'] = 'You must override at least one of the quiz settings.'; $string['nopossibledatasets'] = 'No possible datasets'; $string['noquestionintext'] = 'The question text does not contain any embedded questions'; $string['noquestions'] = 'No questions have been added yet'; @@ -492,6 +502,14 @@ $string['outofshort'] = '$a->grade/$a->maxgrade'; $string['overallfeedback'] = 'Overall feedback'; $string['overdue'] = 'Overdue'; +$string['overridedeletegroupsure'] = 'Are you sure you want to delete the override for group $a?'; +$string['overridedeleteusersure'] = 'Are you sure you want to delete the override for user $a?'; +$string['override'] = 'Override'; +$string['overrides'] = 'Overrides'; +$string['overridegroup'] = 'Override group'; +$string['overrideuser'] = 'Override user'; +$string['overrideusereventname'] = '$a->quiz - Override'; +$string['overridegroupeventname'] = '$a->quiz - $a->group'; $string['pagesize'] = 'Attempts shown per page:'; $string['paragraphquestion'] = 'Paragraph Question not supported at line $a. The question will be ignored'; $string['parent'] = 'Parent'; @@ -552,6 +570,7 @@ $string['quiz:grade'] = 'Grade quizzes manually'; $string['quiz:ignoretimelimits'] = 'Ignores time limit on quizzes'; $string['quiz:manage'] = 'Manage quizzes'; +$string['quiz:manageoverrides'] = 'Manage quiz overrides'; $string['quiz:preview'] = 'Preview quizzes'; $string['quiz:regrade'] = 'Regrade quiz attempts'; $string['quiz:reviewmyattempts'] = 'Review your own attempts'; @@ -632,6 +651,7 @@ $string['responses'] = 'Responses'; $string['results'] = 'Results'; $string['reuseifpossible'] = 'reuse previously removed'; +$string['reverttodefaults'] = 'Revert to quiz defaults'; $string['review'] = 'Review'; $string['reviewafter'] = 'Allow review after quiz is closed'; $string['reviewalways'] = 'Allow review at any time'; @@ -659,6 +679,7 @@ $string['savegrades'] = 'Save grades'; $string['savemyanswers'] = 'Save my answers'; $string['savenosubmit'] = 'Save without submitting'; +$string['saveoverrideandstay'] = 'Save and enter another override'; $string['savequiz'] = 'Save this whole quiz'; $string['score'] = 'Raw score'; $string['scores'] = 'Scores'; @@ -752,6 +773,7 @@ $string['upgradesure'] = '
In particular the quiz module will perform an extensive change of the quiz tables and this upgrade has not yet been sufficiently tested. You are very strongly urged to backup your database tables before proceeding.
'; $string['url'] = 'URL'; $string['usedcategorymoved'] = 'This category has been preserved and moved to the site level because it is a published category still in use by other courses.'; +$string['useroverrides'] = 'User Overrides'; $string['validate'] = 'Validate'; $string['viewallanswers'] = 'View $a quiz attempts'; $string['viewallreports'] = 'View reports for $a attempts'; Index: theme/standardold/style/mod_quiz.css =================================================================== RCS file: /cvsroot/moodle/moodle/theme/standardold/style/mod_quiz.css,v retrieving revision 1.1 diff -u -r1.1 mod_quiz.css --- theme/standardold/style/mod_quiz.css 12 Jan 2010 17:26:51 -0000 1.1 +++ theme/standardold/style/mod_quiz.css 5 Mar 2010 05:06:52 -0000 @@ -1203,4 +1203,21 @@ #categoryquestions .header { border: 0 none; } - +#mod-quiz-overrides div#quizoverrides{ + margin-top: 1.5em; +} +#mod-quiz-overrides div#quizoverrides table{ + width: 100%; +} +#mod-quiz-overrides div#quizoverrides table .colname{ + width: 25%; +} +#mod-quiz-overrides div#quizoverrides table .colvalue{ + width: 50%; +} +#mod-quiz-overrides div#quizoverrides table .colaction{ + width: 10%; +} +#mod-quiz-overrides div#quizoverrides .buttons{ + text-align: center; +} Index: mod/quiz/override_form.php =================================================================== RCS file: mod/quiz/override_form.php diff -N mod/quiz/override_form.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ mod/quiz/override_form.php 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,212 @@ +. + + +/** + * Settings form for overrides in the quiz module. + * + * @package mod-quiz + * @author Matt Petro + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once $CFG->libdir.'/formslib.php'; + +class quiz_override_form extends moodleform { + + protected $cm; // course module object + protected $quiz; // quiz object + protected $context; // context object + protected $groupmode; // editing group override (true) or user override (false) + protected $groupid; // groupid, if provided + protected $userid; // userid, if provided + + public function quiz_override_form($submiturl, $cm, $quiz, $context, $groupmode, $override) { + + $this->cm = $cm; + $this->quiz = $quiz; + $this->context = $context; + $this->groupmode = $groupmode; + $this->groupid = empty($override->groupid)? 0 : $override->groupid; + $this->userid = empty($override->userid)? 0 : $override->userid; + + parent::moodleform($submiturl, null, 'post'); + + } + + public function definition() { + global $CFG, $USER, $DB; + + $cm = $this->cm; + $mform = $this->_form; + + $mform->addElement('header', 'override', get_string('override', 'quiz')); + + if ($this->groupmode) { + // group override + if ($this->groupid) { + // There is already a groupid, so freeze the selector + $groupchoices = array(); + $groupchoices[$this->groupid] = groups_get_group_name($this->groupid); + $mform->addElement('select', 'groupid', get_string('overridegroup', 'quiz'), $groupchoices); + $mform->freeze('groupid'); + } else { + // Prepare the list of groups + $groups = groups_get_all_groups($cm->course, null, $cm->groupingid); + if (empty($groups)) { + $groups = array(); + } + + $groupchoices = array(); + foreach ($groups as $group) { + $groupchoices[$group->id] = $group->name; + } + unset($groups); + + if (count($groupchoices) == 0) { + $groupchoices[0] = get_string('none'); + } + + $mform->addElement('select', 'groupid', get_string('overridegroup', 'quiz'), $groupchoices); + $mform->addRule('groupid', get_string('required'), 'required', null, 'client'); + } + } else { + //user override + if ($this->userid) { + // There is already a userid, so freeze the selector + $user = $DB->get_record('user', array('id'=>$this->userid)); + $userchoices = array(); + $userchoices[$this->userid] = fullname($user); + $mform->addElement('select', 'userid', get_string('overrideuser', 'quiz'), $userchoices); + $mform->freeze('userid'); + } else { + // Prepare the list of users + if (!empty($cm->groupingid)) { + $groups = groups_get_all_groups($cm->course, 0, $cm->groupingid); + $groups = array_keys($groups); + } else { + $groups = null; + } + $users = get_users_by_capability($this->context, 'mod/quiz:attempt', 'u.id,u.firstname,u.lastname,u.email' , + 'firstname ASC, lastname ASC', '', '', $groups, '', false, true); + if (empty($users)) { + $users = array(); + } + + $userchoices = array(); + foreach ($users as $id=>$user) { + if (empty($invalidusers[$id]) || (!empty($override) && $id == $override->userid)) { + $userchoices[$id] = fullname($user) . ', ' . $user->email; + } + } + unset($users); + + if (count($userchoices) == 0) { + $userchoices[0] = get_string('none'); + } + $mform->addElement('searchableselector', 'userid', get_string('overrideuser', 'quiz'), $userchoices); + $mform->addRule('userid', get_string('required'), 'required', null, 'client'); + } + } + + // Password + // This field has to be above the date and timelimit fields, + // otherwise browsers will clear it when those fields are changed + $mform->addElement('passwordunmask', 'password', get_string('requirepassword', 'quiz')); + $mform->setType('password', PARAM_TEXT); + $mform->setHelpButton('password', array('requirepassword', get_string('requirepassword', 'quiz'), 'quiz')); + $mform->setDefault('password', $this->quiz->password); + + // Open and close dates. + $mform->addElement('date_time_selector', 'timeopen', get_string('quizopen', 'quiz'), array('optional' => true)); + $mform->setHelpButton('timeopen', array('timeopen', get_string('quizopen', 'quiz'), 'quiz')); + $mform->setDefault('timeopen', $this->quiz->timeopen); + + $mform->addElement('date_time_selector', 'timeclose', get_string('quizclose', 'quiz'), array('optional' => true)); + $mform->setHelpButton('timeclose', array('timeopen', get_string('quizclose', 'quiz'), 'quiz')); + $mform->setDefault('timeclose', $this->quiz->timeclose); + + // Time limit. + $mform->addElement('duration', 'timelimit', get_string('quiztimer', 'quiz'), array('optional' => true)); + $mform->setHelpButton('timelimit', array('timelimit', get_string('quiztimer','quiz'), 'quiz')); + $mform->setDefault('timelimit', $this->quiz->timelimit); + + // Number of attempts. + $attemptoptions = array('0' => get_string('unlimited')); + for ($i = 1; $i <= QUIZ_MAX_ATTEMPT_OPTION; $i++) { + $attemptoptions[$i] = $i; + } + $mform->addElement('select', 'attempts', get_string('attemptsallowed', 'quiz'), $attemptoptions); + $mform->setHelpButton('attempts', array('attempts', get_string('attemptsallowed','quiz'), 'quiz')); + $mform->setDefault('attempts', $this->quiz->attempts); + + // Submit buttons + $mform->addElement('submit', 'resetbutton', get_string('reverttodefaults','quiz')); + + $buttonarray = array(); + $buttonarray[] = $mform->createElement('submit', 'submitbutton', get_string('save', 'quiz')); + $buttonarray[] = $mform->createElement('submit', 'againbutton', get_string('saveoverrideandstay', 'quiz')); + $buttonarray[] = $mform->createElement('cancel'); + + $mform->addGroup($buttonarray, 'buttonbar', '', array(' '), false); + $mform->closeHeaderBefore('buttonbar'); + + } + + // form verification + public function validation($data, $files) { + global $COURSE, $DB; + $errors = parent::validation($data, $files); + + $mform =& $this->_form; + $quiz = $this->quiz; + + if ($mform->elementExists('userid')) { + if (empty($data['userid'])) { + $errors['userid'] = get_string('required'); + } + } + + if ($mform->elementExists('groupid')) { + if (empty($data['groupid'])) { + $errors['groupid'] = get_string('required'); + } + } + + // Ensure that the dates make sense + if (!empty($data['timeopen']) && !empty($data['timeclose'])) { + if ($data['timeclose'] < $data['timeopen'] ) { + $errors['timeclose'] = get_string('closebeforeopen', 'quiz'); + } + } + + // Ensure that at least one quiz setting was changed + $changed = false; + $keys = array('timeopen','timeclose', 'timelimit', 'attempts', 'password'); + foreach ($keys as $key) { + if ($data[$key] != $quiz->{$key}) { + $changed = true; + break; + } + } + if (!$changed) { + $errors['timeopen'] = get_string('nooverridedata', 'quiz'); + } + + return $errors; + } +} Index: mod/quiz/overrides.php =================================================================== RCS file: mod/quiz/overrides.php diff -N mod/quiz/overrides.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ mod/quiz/overrides.php 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,217 @@ +. + + +/** + * This page handles listing of quiz overrides + * + * @package mod-quiz + * @author Matt Petro + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot.'/mod/quiz/lib.php'); +require_once($CFG->dirroot.'/mod/quiz/locallib.php'); +require_once($CFG->dirroot.'/mod/quiz/override_form.php'); + + +$cmid = required_param('cmid', PARAM_INT); // course module ID, or +$mode = optional_param('mode', 'group', PARAM_ALPHA); // one of 'user' or 'group' + +$groupmode = ($mode == "group"); + +if (! $cm = get_coursemodule_from_id('quiz', $cmid)) { + print_error('invalidcoursemodule'); +} +if (! $quiz = $DB->get_record('quiz', array('id' => $cm->instance))) { + print_error('invalidcoursemodule'); +} + +$url = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id, 'mode'=>$mode)); + +$PAGE->set_url($url); + +require_login($cm->course, false, $cm); + +$context = get_context_instance(CONTEXT_MODULE, $cm->id); + +// Check the user has the required capabilities to list overrides +require_capability('mod/quiz:manageoverrides', $context); + +// Display a list of overrides + +$PAGE->set_title(get_string('overrides', 'quiz')); +echo $OUTPUT->header(); + +// Print heading and tabs (if there is more than one). +$currenttab = 'overrides'; +include('tabs.php'); + +// Fetch all overrides +$conds = array('quiz' => $quiz->id); +if ($groupmode) { + $colname = get_string('group'); + $sql = 'SELECT o.*, g.name + FROM {quiz_overrides} o LEFT JOIN {groups} g + ON o.groupid = g.id + WHERE o.groupid IS NOT NULL + AND o.quiz = ? + ORDER BY g.name'; +} +else { + $colname = get_string('user'); + $sql = 'SELECT o.*, u.firstname, u.lastname, u.id as uid + FROM {quiz_overrides} o LEFT JOIN {user} u + ON o.userid = u.id + WHERE o.userid IS NOT NULL + AND o.quiz = ? + ORDER BY u.lastname, u.firstname'; +} + +$params = array($quiz->id); +$overrides = $DB->get_records_sql($sql, $params); + +// Initialise table +$table = new html_table(); +$table->headspan = array(1,2,1); +$table->colclasses = array('colname','colsetting','colvalue','colaction'); +$table->head = array( + $colname, + get_string('overrides', 'quiz'), + get_string('action'), +); + +$userurl = new moodle_url('/user/view.php', array()); +$groupurl = new moodle_url('/group/overview.php', array('id' => $cm->course)); + +$overridedeleteurl = new moodle_url('/mod/quiz/overridedelete.php'); +$overrideediturl = new moodle_url('/mod/quiz/overrideedit.php'); + +foreach ($overrides as $override) { + + $fields = array(); + $values = array(); + + // check for orphaned overrides + if (!isset($override->name) && !isset($override->uid)) { + // no corresponding user/group record, so remove the override + quiz_delete_override($quiz, $override->id); + continue; + } + + // Format timeopen + if (isset($override->timeopen)) { + $fields[] = get_string('quizopens', 'quiz'); + $values[] = ($override->timeopen > 0)? userdate($override->timeopen) : get_string('noopen', 'quiz'); + } + + // Format timeclose + if (isset($override->timeclose)) { + $fields[] = get_string('quizcloses', 'quiz'); + $values[] = ($override->timeclose > 0)? userdate($override->timeclose) : get_string('noclose', 'quiz'); + } + + // Format timelimit + if (isset($override->timelimit)) { + $fields[] = get_string('timelimit', 'quiz'); + $values[] = ($override->timelimit > 0)? format_time($override->timelimit) : get_string('none', 'quiz'); + } + + // Format number of attempts + if (isset($override->attempts)) { + $fields[] = get_string('attempts', 'quiz'); + $values[] = ($override->attempts > 0)? $override->attempts : get_string('unlimited'); + } + + // Format password + if (isset($override->password)) { + $fields[] = get_string('requirepassword', 'quiz'); + $values[] = ($override->password !== '')? get_string('enabled', 'quiz') : get_string('none', 'quiz'); + } + + // Icons: + + // edit + $editurlstr = $overrideediturl->out(true, array('id' => $override->id)); + $iconstr = '' . + '' . get_string('edit') . ' '; + // duplicate + $copyurlstr = $overrideediturl->out(true, array('id' => $override->id, 'action' => 'duplicate')); + $iconstr .= '' . + '' . get_string('copy') . ' '; + // delete + $deleteurlstr = $overridedeleteurl->out(true, array('id' => $override->id, 'sesskey' => sesskey())); + $iconstr .= '' . + '' . get_string('delete') . ' '; + + if ($groupmode) { + $usergroupstr = '' . $override->name . ''; + } + else { + $usergroupstr = '' . fullname($override) . ''; + } + + if (!empty($table->data)) { + $table->data[] = 'hr'; + } + + $usergroupcell = new html_table_cell(); + $usergroupcell->rowspan = count($fields); + $usergroupcell->text = $usergroupstr; + $actioncell = new html_table_cell(); + $actioncell->rowspan = count($fields); + $actioncell->text = $iconstr; + + for ($i = 0; $i < count($fields); ++$i) { + $row = new html_table_row(); + if ($i == 0) { + $row->cells[] = $usergroupcell; + } + $cell1 = new html_table_cell(); + $cell1->text = $fields[$i]; + $row->cells[] = $cell1; + $cell2 = new html_table_cell(); + $cell2->text = $values[$i]; + $row->cells[] = $cell2; + if ($i == 0) { + $row->cells[] = $actioncell; + } + $table->data[] = $row; + } +} + +// Output the table and button + +echo html_writer::start_tag('div', array('id' => 'quizoverrides')); +if (count($table->data)) { + echo $OUTPUT->table($table); +} + +echo html_writer::start_tag('div', array('class' => 'buttons')); +if ($groupmode) { + echo $OUTPUT->single_button($overrideediturl->out(true, array('action' => 'addgroup', 'cmid' => $cm->id)), + get_string('addnewgroupoverride', 'quiz')); +} else { + echo $OUTPUT->single_button($overrideediturl->out(true, array('action' => 'adduser', 'cmid' => $cm->id)), + get_string('addnewuseroverride', 'quiz')); +} +echo html_writer::end_tag('div'); +echo html_writer::end_tag('div'); + +// Finish the page +echo $OUTPUT->footer(); Index: mod/quiz/overrideedit.php =================================================================== RCS file: mod/quiz/overrideedit.php diff -N mod/quiz/overrideedit.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ mod/quiz/overrideedit.php 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,200 @@ +. + + +/** + * This page handles editing and creation of quiz overrides + * + * @package mod-quiz + * @author Matt Petro + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot.'/mod/quiz/lib.php'); +require_once($CFG->dirroot.'/mod/quiz/locallib.php'); +require_once($CFG->dirroot.'/mod/quiz/override_form.php'); + + +$cmid = optional_param('cmid', 0, PARAM_INT); // course module ID, if new override +$overrideid = optional_param('id', 0, PARAM_INT); // override ID, if editing existing override +$action = optional_param('action', null, PARAM_ALPHA); // if creating new override, one of 'adduser','addgroup', or 'duplicate' +$reset = optional_param('reset', false, PARAM_BOOL); // reset form to defaults + +$override = null; +if ($overrideid) { + + if (! $override = $DB->get_record('quiz_overrides', array('id' => $overrideid))) { + print_error('invalidoverrideid', 'quiz'); + } + if (! $quiz = $DB->get_record('quiz', array('id' => $override->quiz))) { + print_error('invalidcoursemodule'); + } + if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $quiz->course)) { + print_error('invalidcoursemodule'); + } +} else if ($cmid) { + + if (! $cm = get_coursemodule_from_id('quiz', $cmid)) { + print_error('invalidcoursemodule'); + } + if (! $quiz = $DB->get_record('quiz', array('id' => $cm->instance))) { + print_error('invalidcoursemodule'); + } +} else { + print_error('invalidcoursemodule'); +} + +$url = new moodle_url('/mod/quiz/overrideedit.php'); +if ($action) { + $url->param('action', $action); +} +if ($overrideid) { + $url->param('id', $overrideid); +} else { + $url->param('cmid', $cmid); +} + +$PAGE->set_url($url); + +require_login($cm->course, false, $cm); + +$context = get_context_instance(CONTEXT_MODULE, $cm->id); + +// Add or edit an override + +require_capability('mod/quiz:manageoverrides', $context); + +if ($overrideid) { + // editing override + $data = clone $override; +} +else { + // new override + $data = new object(); +} + +// merge quiz defaults with data +$keys = array('timeopen','timeclose', 'timelimit', 'attempts', 'password'); +foreach ($keys as $key) { + if (!isset($data->{$key}) || $reset) { + $data->{$key} = $quiz->{$key}; + } +} + +// If we are duplicating an override, then clear the user/group and override id since they will change +if ($action === 'duplicate') { + $override->id = null; + $override->userid = null; + $override->groupid = null; +} + +$groupmode = !empty($data->groupid) || ($action === 'addgroup' && empty($overrideid)); // true if group-based override + +$overridelisturl = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id)); +if (!$groupmode) { + $overridelisturl->param('mode', 'user'); +} + +// Setup the form. +$mform = new quiz_override_form($url, $cm, $quiz, $context, $groupmode, $override); +$mform->set_data($data); + +if ($mform->is_cancelled()) { + // redirect back to override list + redirect($overridelisturl); +} else if (optional_param('resetbutton', 0, PARAM_ALPHA)) { + // redirect back to current page and reset the form. + $url->param('reset', true); + redirect($url); +} else if ($fromform = $mform->get_data()) { + // Process the data + $fromform->quiz = $quiz->id; + + // Replace unchanged values with null + foreach ($keys as $key) { + if ($fromform->{$key} == $quiz->{$key}) { + $fromform->{$key} = null; + } + } + + // See if we are replacing an existing override + $userorgroupchanged = false; + if (empty($override->id)) { + $userorgroupchanged = true; + } else if (!empty($fromform->userid)) { + $userorgroupchanged = $fromform->userid !== $override->userid; + } else { + $userorgroupchanged = $fromform->groupid !== $override->groupid; + } + if ($userorgroupchanged) { + $conditions = array( + 'quiz' => $quiz->id, + 'userid' => empty($fromform->userid)? null : $fromform->userid, + 'groupid' => empty($fromform->groupid)? null : $fromform->groupid); + if ($oldoverride = $DB->get_record('quiz_overrides', $conditions)) { + // There is an old override, so we merge any new settings on top of + // the older override + foreach ($keys as $key) { + if (is_null($fromform->{$key})) { + $fromform->{$key} = $oldoverride->{$key}; + } + } + // Delete the old override + $DB->delete_records('quiz_overrides', array('id' => $oldoverride->id)); + } + } + + if (!empty($override->id)) { + $fromform->id = $override->id; + $DB->update_record('quiz_overrides', $fromform); + } + else { + unset($fromform->id); + $fromform->id = $DB->insert_record('quiz_overrides', $fromform); + } + + quiz_update_events($quiz, $fromform); + + add_to_log($cm->course, 'quiz', 'edit override', + "overrideedit.php?id=$fromform->id", $quiz->id, $cm->id); + + if (!empty($fromform->submitbutton)) { + // redirect back to override list + redirect($overridelisturl); + } + + // 'again' button pressed, so redirect back to this page + $url->remove_params('cmid'); + $url->param('action', 'duplicate'); + $url->param('id', $fromform->id); + redirect($url); + +} + +// Print the form + +$pagetitle = get_string('editoverride', 'quiz'); +$PAGE->navbar->add($pagetitle); +$PAGE->set_title($pagetitle); + +echo $OUTPUT->header(); +echo $OUTPUT->heading($pagetitle); + +$mform->display(); + +echo $OUTPUT->footer(); Index: mod/quiz/overridedelete.php =================================================================== RCS file: mod/quiz/overridedelete.php diff -N mod/quiz/overridedelete.php --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ mod/quiz/overridedelete.php 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,97 @@ +. + + +/** + * This page handles deleting quiz overrides + * + * @package mod-quiz + * @author Matt Petro + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(__FILE__) . '/../../config.php'); +require_once($CFG->dirroot.'/mod/quiz/lib.php'); +require_once($CFG->dirroot.'/mod/quiz/locallib.php'); +require_once($CFG->dirroot.'/mod/quiz/override_form.php'); + +$overrideid = required_param('id', PARAM_INT); // override ID +$confirm = optional_param('confirm', false, PARAM_BOOL); // already confirmed? + +if (! $override = $DB->get_record('quiz_overrides', array('id' => $overrideid))) { + print_error('invalidoverrideid', 'quiz'); +} +if (! $quiz = $DB->get_record('quiz', array('id' => $override->quiz))) { + print_error('invalidcoursemodule'); +} +if (! $cm = get_coursemodule_from_instance("quiz", $quiz->id, $quiz->course)) { + print_error('invalidcoursemodule'); +} + +$context = get_context_instance(CONTEXT_MODULE, $cm->id); + +require_login($cm->course, false, $cm); + +// Check the user has the required capabilities to modify an override +require_capability('mod/quiz:manageoverrides', $context); + +$url = new moodle_url('/mod/quiz/overridedelete.php', array('id'=>$override->id)); +$confirmurl = new moodle_url($url, array('id'=>$override->id, 'confirm'=>1)); +$cancelurl = new moodle_url('/mod/quiz/overrides.php', array('cmid'=>$cm->id)); + +if (!empty($override->userid)) { + $cancelurl->param('mode', 'user'); +} + +// If confirm is set (PARAM_BOOL) then we have confirmation of intention to delete +if ($confirm) { + // Confirm the session key to stop CSRF + require_sesskey(); + + // Remove the override + quiz_delete_override($quiz, $override->id); + + add_to_log($cm->course, 'quiz', 'delete override', + "overrides.php?cmid=$cm->id", $quiz->id, $cm->id); + + // And redirect + redirect($cancelurl); +} + +// Prepare the page to show the confirmation form +$stroverride = get_string('override', 'quiz'); +$title = get_string('deletecheck', null, $stroverride); + +$PAGE->set_url($url); +$PAGE->navbar->add($title); +$PAGE->set_title($title); +$PAGE->set_heading($stroverride); + +echo $OUTPUT->header(); + +if ($override->groupid) { + $group = $DB->get_record('groups', array('id' => $override->groupid), 'id,name'); + $confirmstr = get_string("overridedeletegroupsure", "quiz", $group->name); +} +else { + $user = $DB->get_record('user', array('id' => $override->userid), 'id,firstname,lastname'); + $confirmstr = get_string("overridedeleteusersure", "quiz", fullname($user)); +} + +echo $OUTPUT->confirm($confirmstr, $confirmurl, $cancelurl); + +echo $OUTPUT->footer();